package org.ubc.cs410.disposal2020.client;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.ubc.cs410.disposal2020.Player;
import org.ubc.cs410.disposal2020.Robot;
import org.ubc.cs410.disposal2020.client.dto.BuildingBoundaryDto;
import org.ubc.cs410.disposal2020.client.dto.GarbageDto;
import org.ubc.cs410.disposal2020.client.dto.MapDto;
import org.ubc.cs410.disposal2020.client.dto.RobotDto;
import org.ubc.cs410.disposal2020.client.view.GarbageMarker;
import org.ubc.cs410.disposal2020.client.view.MapView;
import org.ubc.cs410.disposal2020.client.view.RobotMarker;
import org.ubc.cs410.disposal2020.server.GameCache;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.maps.client.MapOptions;
import com.google.gwt.maps.client.event.MarkerDragEndHandler;
import com.google.gwt.maps.client.event.MarkerDragStartHandler;
import com.google.gwt.maps.client.event.MarkerDragEndHandler.MarkerDragEndEvent;
import com.google.gwt.maps.client.event.MarkerDragStartHandler.MarkerDragStartEvent;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.geom.Size;
import com.google.gwt.maps.client.overlay.Marker;
import com.google.gwt.maps.client.overlay.Polygon;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;

/*
 * This class is executed when user logged in and started/joined a game
 */

public class GameClient implements EntryPoint {
	private MapView map;

	private MapServiceAsync mapService;
	private PlayerServiceAsync playerService;
	private GameServiceAsync gameService;
	private RobotServiceAsync robotService;

	private Map<Long,Marker> robotMarkers = null;
	private Set<GarbageMarker> garbageMarkers = null;

	private LatLng robotStartPt;
	private Polygon robotMoveCircle;

	private Timer timer;
	private Integer turn;

	// GWT module entry point method.
	public void onModuleLoad() {
		initServices();

		initTimer();

		turn = 0;

		MapOptions mapOptions = MapOptions.newInstance();
		mapOptions.setSize(Size.newInstance(850, 550));
		map = new MapView(LatLng.newInstance(49.261594, -123.247998), 14, mapOptions);

		VerticalPanel mapTab = new VerticalPanel();
		mapTab.add(map);

		RootPanel.get("gameMap").add(mapTab);



		updateMarkers();
	}

	private void updateMarkers() {
		map.clearAllOverylay();
		// Load buildings from map
		String mapId = DOM.getElementById("mapId").getInnerHTML();      
		mapService.loadMapById(Long.parseLong(mapId), new AsyncCallback<MapDto>() { 
			@Override
			public void onFailure(Throwable caught) { }
			@Override
			public void onSuccess(MapDto mapDto) {          
				if (mapDto != null) {   
					map.resetMapLocation(mapDto.getCenterLatitude(), mapDto
							.getCenterLongitude(), mapDto.getZoomLevel());      
					for (BuildingBoundaryDto dto : mapDto.getBuildingList()) {
						map.addOverlay(dto);
					}
				}
			}
		});

		robotMarkers = new HashMap<Long,Marker>();
		garbageMarkers = new HashSet<GarbageMarker>();

		playerService.listAllRobots(new AsyncCallback<Set<RobotDto>>() { 
			@Override
			public void onFailure(Throwable caught) { }
			@Override
			public void onSuccess(Set<RobotDto> robots) {          
				if (robots != null) {
					for (RobotDto r : robots) {
						addRobot(r);
					}
				}
			}
		});

		gameService.listAllGarbages(new AsyncCallback<Set<GarbageDto>>() { 
			@Override
			public void onFailure(Throwable caught) { }
			@Override
			public void onSuccess(Set<GarbageDto> gs) {          
				if (gs != null) {
					for (GarbageDto g : gs) {
						addGarbage(g);
					}
				}
			}
		});
	}

	private void initTimer() {
		// Timer to update the map state if there's a new turn
		timer = new Timer() {
			@Override
			public void run() {
				gameService.getTurn(new AsyncCallback<Integer>() {
					@Override
					public void onFailure(Throwable caught) { }
					@Override
					public void onSuccess(Integer result) {
						if (result != null) {
							if (result==-1)
								reload();
							if (result>turn) {
								turn = result;
								updateMapState();
							}	              
						}
					}
				});
			}
		};
		timer.scheduleRepeating(1000);

	}

	private void updateMapState() {
		updateLabels();
		updateMarkers();
	}

	private void updateLabels() {
		DOM.getElementById("spnTurn").setInnerHTML(new Integer(turn).toString());
		playerService.listScores(new AsyncCallback<Map<String,Integer>>() { 
			@Override
			public void onFailure(Throwable caught) { }
			@Override
			public void onSuccess(Map<String,Integer> scores) {          
				if (scores != null) {
					for (Entry<String,Integer> e : scores.entrySet()) {
						if (e.getKey().equals(DOM.getElementById("userEmail").getInnerHTML())) {
							DOM.getElementById("spnYourScore").setInnerHTML(e.getValue().toString());
						} else {
							DOM.getElementById("spnOpScore").setInnerHTML(e.getValue().toString());
						}
					}
				}
			}
		});
	}

	private void addRobot(RobotDto robot) {
		GWT.log("Adding Robot marker to position "+robot.getLocation().toString(),null);
		final RobotMarker m = new RobotMarker(robot);
		map.getMap().addOverlay(m);

		m.addMarkerDragStartHandler(new MarkerDragStartHandler() {
			public void onDragStart(MarkerDragStartEvent event) {
				robotMoveStart((RobotMarker) event.getSender());
			}
		});

		m.addMarkerDragEndHandler(new MarkerDragEndHandler() {
			public void onDragEnd(MarkerDragEndEvent event) {
				robotMoveEnd((RobotMarker) event.getSender());
			}
		});

		//	  robotMarkers.put(robot.getId(),m);
		//	  robotInfo.put(robot.getId(), robot);
	}

	private void robotMoveStart(RobotMarker m) {
		robotStartPt = m.getLatLng();
		robotMoveCircle = map.addCircle(m.getLatLng());
	}

	private void robotMoveEnd(RobotMarker m) {
		LatLng end = m.getLatLng();
		double dis = robotStartPt.distanceFrom(end);
		if (dis > 200) {
			m.setLatLng(robotStartPt);
		} else {
			GWT.log("Moving robot to "+end.toString(),null);
			robotService.moveRobot(m.getRobot().getKey(), end.getLatitude(), end.getLongitude(),
					new AsyncCallback<Void>() { 
				@Override
				public void onFailure(Throwable caught) { }
				@Override
				public void onSuccess(Void result) {

				}
			});
			m.setLatLng(end);
			//disable the robot after being moved in a turn
			m.setDraggingEnabled(false);

			//TODO: set marker to draggable at turn end; previous commit not working, since
			//needs the server and client side turn implementation which gwwfps is working on


			for (GarbageMarker g : garbageMarkers) {
				if (g.getLatLng().distanceFrom(end) < 50) {
					map.getMap().removeOverlay(g);
					garbageMarkers.remove(g);
				}
			}

		}

		// TODO: add robot in the db
		map.getMap().removeOverlay(robotMoveCircle);
	}

	private void addGarbage(GarbageDto g) {
		final GarbageMarker m = new GarbageMarker(g);
		map.getMap().addOverlay(m);
		garbageMarkers.add(m);

		//		  robotMarkers.put(robot.getId(),m);
		//		  robotInfo.put(robot.getId(), robot);
	}

	private void initServices() {
		mapService = (MapServiceAsync) GWT.create(MapService.class);
		ServiceDefTarget mapEp = (ServiceDefTarget) mapService;
		mapEp.setServiceEntryPoint("/services/map");

		playerService = (PlayerServiceAsync) GWT.create(PlayerService.class);
		ServiceDefTarget playerEp = (ServiceDefTarget) playerService;
		playerEp.setServiceEntryPoint("/services/player");

		gameService = (GameServiceAsync) GWT.create(GameService.class);
		ServiceDefTarget gameEp = (ServiceDefTarget) gameService;
		gameEp.setServiceEntryPoint("/services/game");    

		robotService = (RobotServiceAsync) GWT.create(RobotService.class);
		ServiceDefTarget robotEp = (ServiceDefTarget) robotService;
		robotEp.setServiceEntryPoint("/services/robot");
	}

	private native void reload() /*-{ 
  $wnd.location.reload(); 
 }-*/; 
}
